﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Cci;

using Logging;

using PartialExpressionCompleter.Query;
using PartialExpressionCompleter.Util;

namespace PartialExpressionCompleter.Experiment
{
    class AssignmentQueryExperiment : AssignmentExperiment
    {
        public AssignmentQueryExperiment(IAssembly unit) : base(unit) { }

        public override string FileExtension
        {
            get { return "assignment"; }
        }
        protected override void DoAssignmentAction(QueryContext<IAssignment> context)
        {
            var assignment = context.OriginalExpression;

            var source = assignment.Source;
            var target = assignment.Target;

            var se = source.AsExpression(context);
            var te = target.AsExpression(context);

            Logger.PrintLine();
            Logger.PrintLine("Assignment: " + te + " := " + se);

            if (!te.Type.IsAssignableFrom(se.Type))
            {
                if (!se.ToString().Equals("null"))
                {
                    Logger.PrintLine("Found invalid assignment from "
                        + se.Type + " to " + te.Type);
                }
                return;
            }
            foreach (var ancestorPair in te.GetAncestors().Select((t, i) => t.PairWith(i))
                                           .CartesianProduct(se.GetAncestors().Select((s, i) => s.PairWith(i))))
            {
                var targetTup = ancestorPair.Item1;
                var sourceTup = ancestorPair.Item2;

                var targetCut = targetTup.Item2;
                var sourceCut = sourceTup.Item2;

                var targetQuery = targetTup.Item1;
                var sourceQuery = sourceTup.Item1;

                var start = DateTime.Now;
                var depth = 1;
                var results = targetQuery.GetDescendents(context, depth).Where(e => e.IsLValue).GroupBy(e => e.Type.PairWith(e.BaseCost))
                                         .CartesianProduct(sourceQuery.GetDescendents(context, depth).GroupBy(e => e.Type.PairWith(e.BaseCost)))
                                .Where(t => t.Item1.Key.Item1.IsAssignableFrom(t.Item2.Key.Item1))
                                .ToList();

                te.BaseCost = targetCut * 2 * ScoreSwitches.GetMultiplier(ScoreTypes.Depth);
                se.BaseCost = sourceCut * 2 * ScoreSwitches.GetMultiplier(ScoreTypes.Depth);

                Logger.PrintLine("Query: " + targetQuery + " := " + sourceQuery);
                Logger.Print("Rank: "
                    + ComputeScore(results, context, te.PairWith(se))
                    + " / " + results.Sum(g => g.Item1.Count() * g.Item2.Count())
                    + " [target cut=" + targetCut + ", source cut=" + sourceCut + "]");
                var end = DateTime.Now;
                Logger.PrintLine("; time: " + (end - start).TotalSeconds);
            }
            Logger.PrintLine();
        }
        protected int ComputeScore(List<Tuple<IGrouping<Tuple<ITypeDefinition, int>,
                                                    Expression>,
                                              IGrouping<Tuple<ITypeDefinition, int>,
                                                    Expression>>> possibleAssigns,
                                   QueryContext<IAssignment> context,
                                   Tuple<Expression, Expression> correctAnswer)
        {
            try
            {
                var correctCost = ComputeScore(correctAnswer, context);
                var scoredExps = possibleAssigns
                    .Where(e => e.Item1.Key.Item2 + e.Item2.Key.Item2 <= correctCost)
                    .SelectMany(e => ComputeScores(e, context))
                    .OrderBy(t => t.Item2)
                    .ToList();
                return (scoredExps.TakeWhile(t => !t.Item1.Equals(correctAnswer))
                                  .Count() + 1);
            }
            catch (InvalidOperationException)
            {
                return possibleAssigns.Count + 1;
            }
        }
        protected static IEnumerable<Tuple<Tuple<Expression, Expression>, int>>
                         ComputeScores(Tuple<IGrouping<Tuple<ITypeDefinition, int>,
                                                        Expression>,
                                             IGrouping<Tuple<ITypeDefinition, int>,
                                                        Expression>> assigns,
                                       QueryContext<IAssignment> context)
        {
            var targetGroup = assigns.Item1;
            var sourceGroup = assigns.Item2;

            var targetKey = targetGroup.Key;
            var sourceKey = sourceGroup.Key;

            var targetType = targetKey.Item1;
            var sourceType = sourceKey.Item1;

            var baseCosts = targetKey.Item2 + sourceKey.Item2;
            int typeDist = 0;
            if (ScoreSwitches.IsEnabled(ScoreTypes.TypeDistance))
            {
                typeDist = targetType.DistanceFrom(sourceType) * ScoreSwitches.GetMultiplier(ScoreTypes.TypeDistance);
            }
            var totalBase = baseCosts + typeDist;

            if (ScoreSwitches.IsEnabled(ScoreTypes.AbstractTypeDistance))
            {
                var targetsWithMeanings = targetGroup
                    .Select(t => t.PairWith(context.GetMeaningsInContext(t))).ToList();
                var sourcesWithMeanings = sourceGroup
                    .Select(s => s.PairWith(context.GetMeaningsInContext(s))).ToList();
                foreach (var targetTup in targetsWithMeanings)
                {
                    var target = targetTup.Item1;
                    var tMeanings = targetTup.Item2;
                    foreach (var sourceTup in sourcesWithMeanings)
                    {
                        var source = sourceTup.Item1;
                        var sMeanings = sourceTup.Item2;
                        var absTypeScore = tMeanings.Overlaps(sMeanings)
                            ? (sbyte)0
                            : ScoreSwitches.GetMultiplier(ScoreTypes.AbstractTypeDistance);

                        yield return (target.PairWith(source))
                            .PairWith(totalBase + absTypeScore);
                    }
                }
            }
            else
            {
                foreach (var target in targetGroup)
                {
                    foreach (var source in sourceGroup)
                    {
                        yield return (target.PairWith(source)).PairWith(totalBase);
                    }
                }
            }
        }
        protected static int ComputeScore(Tuple<Expression, Expression> assignment,
            QueryContext<IAssignment> context)
        {
            var target = assignment.Item1;
            var source = assignment.Item2;

            return ScoreSwitches.GetMultiplier(ScoreTypes.TypeDistance)
                    * target.Type.DistanceFrom(source.Type)
                + target.BaseCost + source.BaseCost
                + (ScoreSwitches.IsEnabled(ScoreTypes.AbstractTypeDistance)
                 && context.GetMeaningsInContext(target)
                    .Overlaps(context.GetMeaningsInContext(source))
                   ? (sbyte)0 : ScoreSwitches.GetMultiplier(ScoreTypes.AbstractTypeDistance));
        }
    }
}
